Практикум 6. Сигналы и мотивы - 1

Промотор сигма-фактора 54 РНК-полимеразы эубактерий - участок ДНК, c которого инициируется транскрипция после посадки на него сигма-фактора. Консенсус: 5'-TTGGCACGN4-TTGC-3'. Сигнал адресован бактериальной РНК-полимеразе.

Сначала, при сканировании ДНК РНК-полимеразой, альфа-субъединица узнает небольшой участок промотора. Затем привлекается сигма-субъединица (сигма-фактор 54) и специфично связывается с промотором, после чего с образованием закрытого комплекса (термин "закрытый" означает, что ДНК находится в двуцепочечном состоянии) и инициируется транскрипция. Сигнал является достаточно высокоэффективным.

Источники:

  1. Victoria Shingler, Signal sensory systems that impact σ54-dependent transcription, FEMS Microbiology Reviews, Volume 35, Issue 3, May 2011, Pages 425–440, https://doi.org/10.1111/j.1574-6976.2010.00255.x
  2. Burrows PC, Severinov K, Ishihama A, Buck M, Wigneshweraraj SR. Mapping sigma 54-RNA polymerase interactions at the -24 consensus promoter element. J Biol Chem. 2003 Aug 8;278(32):29728-43. doi: 10.1074/jbc.M303596200. Epub 2003 May 15. PMID: 12750380.
  3. Гены по Льюину / Дж.Кребс, Э. Голдштейн, С. Килпатрик. Изд. "Лаборатория знаний", 2018

В этом практикуме использован скрипт Дмитрия Звездина.

Построение позиционной весовой матрицы

In [22]:
import numpy as np
import pandas as pd
import requests
from scipy.stats import mannwhitneyu
from IPython.display import Image
In [23]:
genes_table = pd.read_csv("human-genes.tsv", sep="\t")
sequences = []
i = 300
req_count = 0
while req_count < 100:
    chromosome = genes_table.loc[i, "#chrom"]
    start = genes_table.loc[i, "thickStart"]
    strand = genes_table.loc[i, "strand"]
    i += 1
    if str(strand) == "+":
        link = f"https://rest.ensembl.org/sequence/region/human/{chromosome}:{start-6}..{start+400}:1?expand_3prime=0;expand_5prime=0"
        request = requests.get(link, headers={ "Content-Type" : "text/x-fasta"})
    else:
        continue
    if "error" in request.text:
        continue
    else:
        req_count += 1
        sequence = "".join(request.text.split("\n")[1:])
        sequences.append(sequence)
        
with open("sequences.txt", "w") as file:
    print(*sequences, sep="\n", file=file)

С помощью вышеприведенного скрипта было выделено 100 частичных последовательностей генов. Каждая последовательность содержит в себе 7 нуклеотидов до старт-кодона ATG, сам ATG и 397 нуклеотидов после него.

In [24]:
sequences_clear=[]
sequences_test_negative=[]
with open("sequences.txt", "r") as file:
    sequences = file.readlines()
    for sequence in sequences:
        sequence_ = sequence[:13]
        if sequence_[7:10] == "ATG":
            sequences_clear.append(sequence_)
        ATG_coord_fake = sequence[200:].find("ATG")
        sequences_test_negative.append(sequence[200:][ATG_coord_fake-7:ATG_coord_fake+6])
sequences_train = sequences_clear[:40]
sequences_test = sequences_clear[40:]
sequences_test_negative = [seq for seq in sequences_test_negative if len(seq) == 13]
sequences_test_negative = sequences_test_negative[:36]

Из полученных последовательностей были вырезаны куски длины 13 нуклеотидов: 7 нуклеотидов до координаты старт-кодона ATG, сам старт-кодон и 6 нуклеотидов после него. Таким образом была создана выборка последовательностей, предположительно содержащих последовательность Козак, для построения PWM.

В качестве негативного контроля были вырезаны участки генов, отстоящие минимум на 200 нуклеотидов от начала последовательности и содержащие кодон ATG, не являющийся старт-кодоном. Они также содержат 7 нуклеотидов до координаты старт-кодона ATG, сам старт-кодон и 6 нуклеотидов после него.

In [25]:
class PWM():
    def __init__(self, e=1, freq=[0.298, 0.298, 0.205, 0.205]):
        self.e = e
        self.positions = {"A": 0, "T": 1, "G": 2, "C": 3}
        self.frequencies = np.array(freq).reshape((4, 1))
        
    def fit(self, seq_list):
        self.seq_arr = np.array(list(map(list, seq_list)), dtype="str")
        self.n_pos = len(seq_list[0])
        self.n_seq = len(seq_list)
        A_n = np.count_nonzero(self.seq_arr == "A", axis=0)
        T_n = np.count_nonzero(self.seq_arr == "T", axis=0)
        G_n = np.count_nonzero(self.seq_arr == "G", axis=0)
        C_n = np.count_nonzero(self.seq_arr == "C", axis=0)
        self.matrix = np.stack([A_n, T_n, G_n, C_n], axis=0).astype("float")
        pseudocount = self.e / 4
        self.matrix += pseudocount
        self.matrix = self.matrix / (self.n_seq+self.e)
        self.pwm_matrix = np.log(self.matrix / self.frequencies)
        
    def test(self, seq):
        weight = 0
        for i, nuc in enumerate(list(seq)):
            weight += self.pwm_matrix[self.positions[nuc], i]
        return weight
    
    def ic_calculate(self):
        self.ic_matrix = self.matrix * np.log2(self.matrix / self.frequencies)
        
    def return_pwm(self):
        return pd.DataFrame(self.pwm_matrix, columns = np.arange(1, self.n_pos + 1), index = ["A", "T", "G", "C"])
    
    def return_ic(self):
        return pd.DataFrame(self.ic_matrix, columns = np.arange(1, self.n_pos + 1), index = ["A", "T", "G", "C"])

В приведенном выше коде позиционная весовая матрица построена как класс с методами, вычисляющими веса тестовых последовательностей и матрицу информационного содержания. Базовый GC-состав составляет 41% (ссылка на источник).

In [26]:
pwm = PWM()
pwm.fit(sequences_train)
real_scores = []
fake_scores = []
for seq, fake_seq in zip(sequences_test, sequences_test_negative):
    real_scores.append(pwm.test(seq))
    fake_scores.append(pwm.test(fake_seq))
pwm.return_pwm()
Out[26]:
1 2 3 4 5 6 7 8 9 10 11 12 13
A -0.278287 0.344902 -0.082542 -0.082542 0.401255 -0.278287 -0.521909 1.192200 -3.889205 -3.889205 0.081087 -0.521909 -2.279767
T -0.844682 -1.324255 -0.082542 -1.055991 -0.521909 -0.521909 -2.279767 -3.889205 1.192200 -3.889205 -0.521909 -0.392697 -0.670329
G 0.095797 0.376699 0.291541 0.455171 0.198451 0.198451 0.595753 -3.515121 -3.515121 1.566283 0.527930 0.595753 0.718985
C 0.718985 -0.018614 -0.147825 0.376699 -0.470599 0.527930 0.718985 -3.515121 -3.515121 -3.515121 -0.296245 0.198451 0.659266
In [27]:
print(f"Средний вес положительного контроля: {np.mean(real_scores)}, средний вес отрицательного контроля: {np.mean(fake_scores)}.")
print(mannwhitneyu(real_scores, fake_scores))
Средний вес положительного контроля: 4.931959584335049, средний вес отрицательного контроля: 3.772752886404346.
MannwhitneyuResult(statistic=863.0, pvalue=0.01568979942263371)

После построения PWM она была протестирована на тестовой выборке и негативном контроле (по 36 последовательностей). Принадлеженость вычисленных весов последовательностей для тестовой выборки и отрицательного контроля к одному распределению проверялась с помощью непараметрического теста Манна-Уитни.

При пороге значимости p = 0.05 гипотеза о принадлежности наборов весов одному распределению была отвергнута (p = 0.015). Веса последовательностей из тестового набора значимо выше весов последовательностей негативного контроля.

Матрица информационного содержания

In [28]:
pwm.ic_calculate()
pwm.return_ic()
Out[28]:
1 2 3 4 5 6 7 8 9 10 11 12 13
A -0.090578 0.209351 -0.032675 -0.032675 0.257676 -0.090578 -0.133145 1.688518 -0.034213 -0.034213 0.037806 -0.133145 -0.100275
T -0.156043 -0.151442 -0.032675 -0.157921 -0.133145 -0.133145 -0.100275 -0.034213 1.688518 -0.034213 -0.133145 -0.113999 -0.147421
G 0.031181 0.162376 0.115410 0.212217 0.071576 0.071576 0.319688 -0.030922 -0.030922 2.218334 0.264717 0.319688 0.436415
C 0.436415 -0.005403 -0.037712 0.162376 -0.086936 0.264717 0.436415 -0.030922 -0.030922 -0.030922 -0.065151 0.071576 0.376968

Выше представлена матрица информационного содержания последовательностей, использованных для построения PWM. На рисунке ниже показан LOGO.

In [29]:
Image(filename="logo.png")
Out[29]: